/*
 * @Author: hongbin
 * @Date: 2022-06-13 19:40:04
 * @LastEditors: hongbin
 * @LastEditTime: 2022-09-15 21:06:48
 * @Description: 适用于本项目的助手函数
 */
import THREE, { collideParams } from "../";
import { PointerLockControls } from "three/examples/jsm/controls/PointerLockControls";
import { GLTF } from "three/examples/jsm/loaders/GLTFLoader";
import Home from "../Stars/Home";
import Forest from "../Stars/Forest";
import Stats from "stats.js";
import { acterScaleMultiple } from "../../constants";
import { frontOfFoot, kneeCollide, uphillCollide } from "../utils/RayControl";
import { acterWrap } from "../object";
import { TPlanet } from "../types";

const { Vector3, AnimationMixer, Box3, Clock } = THREE;

export type positionAxis = "x" | "y" | "z";

const axisArr: positionAxis[] = ["x", "y", "z"];
/**
 * 随机从 x,y,z轴中选一个方向
 */
export function randomAxis(): positionAxis {
    return axisArr[Math.floor(Math.random() * 3)];
    // return 'x';
}

/**
 * @description: 对象的某属性 逐帧变换
 * @param {*} mash 独享
 * @param {*} prop 属性
 * @param {*} valRegion 变化区间
 * @param {*} paragraph 分多少段完成变换
 * @param {*} onComplete 执行完回掉函数
 * @return {*}
 */
export const animationFrameTrans = (
    mash: THREE.Material,
    prop: Omit<keyof THREE.Material, "isMaterial">,
    valRegion = [0, 1],
    paragraph = 10,
    onComplete: () => any | (() => void)
) => {
    const diff = valRegion[1] - valRegion[0];
    const count = diff / paragraph;
    //@ts-ignore
    mash[prop] = valRegion[0];

    const tick = () => {
        //@ts-ignore
        mash[prop] += count;
        //@ts-ignore
        if (mash[prop] <= valRegion[1]) {
            requestAnimationFrame(tick);
        } else onComplete && onComplete();
    };
    tick();
};

export type Intersection = THREE.Intersection<THREE.Object3D<THREE.Event>>[];

export type TCntrols = {
    getDirection: (v: THREE.Vector3) => THREE.Vector3;
    getObject: () => THREE.PerspectiveCamera | THREE.Object3D;
};

const { PI } = Math;
let dir = new THREE.Vector3();
/**
 * @description:  传入角度即方向 判断附近有没有障碍物 返回是否有碰撞或返回障碍物
 * @param {controls} controls  控制器
 * @param {objects} objects  碰撞检测的对象
 * @param {eyeHeight} eyeHeight  眼睛的高度 决定是脚底或是头部或事腰部 任意高度
 * @param {angle} angle  射线旋转角度 决定前后左右
 * @param {far} far  检测的距离
 * @return  Object3D[]
 */
export const collideCheck = (
    controls: TCntrols,
    objects: THREE.Object3D[],
    eyeHeight: number,
    angle: number,
    far: number = 5
) => {
    let rotationMatrix = new THREE.Matrix4();
    //绕y轴旋转 默认可以理解为朝前方 旋转90度后就朝向左方 这样来实现目标点在当前点的某个方向
    rotationMatrix.makeRotationY((angle * PI) / 180);
    //返回摄像机的观看方向
    controls.getDirection(dir);
    //将该向量乘以四阶矩阵m（第四个维度隐式地为1），并按角度进行划分。
    dir.applyMatrix4(rotationMatrix);
    dir.normalize();

    // dir.y -= eyeHeight;
    const raycaster = new THREE.Raycaster(controls.getObject().position.clone(), dir, 0, far);
    raycaster.ray.origin.y -= eyeHeight;
    // const length = 6;
    // const hex = 0xffff00;

    // const arrowHelper = new THREE.ArrowHelper(raycaster.ray.origin, dir, length, hex);
    // arrowHelper.position.y = 10;
    // //@ts-ignore
    // acterWrap.add(arrowHelper);
    // console.log(controls.getObject().position.clone(), dir);
    // console.log(raycaster);

    const intersections = raycaster.intersectObjects(objects, true);
    // intersections.length && console.log(intersections[0].object.userData.index);
    return intersections;
};

// const around = {
//     forward: { axis: "z", vector: 1, angle: 0 },
//     backward: { axis: "z", vector: -1, angle: 180 },
//     left: { axis: "x", vector: -1, angle: 90 },
//     right: { axis: "x", vector: 1, angle: 270 },
// };

// controls: PointerLockControls,
// objects: THREE.Object3D[],
// eyeHeight: number

/**
 * @description: 检测四周有没有被碰撞--区别于按下判断一次对应一侧 只是贴身的碰撞被推着走
 * @return {*}
 */
export function checkAllAroundCollide(
    collideCollideBlocks: Set<number>,
    ...rest: [PointerLockControls, THREE.Object3D[], number]
) {
    for (let angle = 0; angle <= 3; angle++) {
        const object = collideCheck(...rest, angle * 90, 4);
        object.length && collideCollideBlocks.add(object[0].object.userData.index);
    }
}

export const randomColor = () =>
    `#${Math.floor(Math.random() * 0xffffff)
        .toString(16)
        .padEnd(6, (Math.random() * 0xf).toString(16)[0])}`;

let _object = new Vector3();
/**
 * 使用Box3 返回物体的长宽高
 */
export function getSize(mesh: THREE.Object3D) {
    let _object = new Vector3();
    // console.log(new Box3().setFromObject(mesh));
    return new Box3().setFromObject(mesh).getSize(_object);
}

function computeQuaternion(old: THREE.Quaternion, ter: THREE.Quaternion) {
    const x = distance(old.x, ter.x);
    const y = distance(old.y, ter.y);
    const z = distance(old.z, ter.z);
    const w = distance(old.w, ter.w);
    return {
        x,
        y,
        z,
        w,
        prev: old.clone(),
    };
}

export function scaleToTarget(controls: { syncRotate: boolean }, object: THREE.Object3D, target: THREE.Object3D) {
    //计算差值
    const { x, y, z, w, prev } = computeQuaternion(object.quaternion, target.quaternion);

    const _euler = new THREE.Euler(0, 0, 0, "YXZ");
    const _PI_2 = Math.PI / 2;
    const maxPolarAngle = Math.PI / 2;
    const minPolarAngle = Math.PI / 2;

    let count = 0;
    //逐帧递增
    const tick = () => {
        if (count < 10) requestAnimationFrame(tick);
        else controls.syncRotate = true;
        prev.x += x / 10;
        prev.y += y / 10;
        prev.z += z / 10;
        prev.w += w / 10;
        _euler.setFromQuaternion(prev);

        _euler.x = Math.max(Math.PI / 2 - maxPolarAngle, Math.min(Math.PI / 2 - minPolarAngle, _euler.x));
        object.quaternion.setFromEuler(_euler);
        count++;
    };
    tick();
}

function distance(x1: number, x2: number) {
    //都是负数  -3 => -5 = -2
    if (x1 < 0 && x2 < 0) {
        return (x2 * -1 - x1 * -1) * -1;
    }
    return x2 - x1;
}

//TODO 设计一个类专为四个方向检测服务 不一直创建新射线对象
class roundCollide {}

export function AnimationPlayer() {
    let timer = 0;
    let mixer: THREE.AnimationMixer = {
        //@ts-ignore
        update: () => {},
        //@ts-ignore
        setTime: () => {},
    };
    let playing = false;
    const clock = new Clock();
    const animationActions: THREE.AnimationAction[] = [];

    const reset = () => {
        // console.log(mixer);
        mixer.setTime(0);
        // console.log(mixer);

        // animationActions.forEach((animationAction) => {
        //     animationAction.reset();
        //     console.log(animationAction);
        // });
    };

    const stop = () => {
        if (!playing) return;
        cancelAnimationFrame(timer);
        reset();
        playing = false;
    };

    function init(root: THREE.Object3D, animations: THREE.AnimationClip[]) {
        // gltf = insetGltf;
        mixer = new AnimationMixer(root);
        animations.forEach((animate) => {
            const animationAction = mixer.clipAction(animate).play();
            animationActions.push(animationAction);
        });
    }

    function start() {
        if (playing) return;
        stop();
        playing = true;
        // if (!mixer) throw Error("mixer 尚未初始化");
        const animate = () => {
            timer = requestAnimationFrame(animate);
            mixer.update(clock.getDelta());
        };
        animate();
    }

    return { init, start, stop };
}

interface IModelSurround {
    /**
     * 模型 或者一个返回模型的函数
     */
    model: THREE.Object3D | (() => THREE.Object3D);
    /**
     * 半径
     */
    radius: number;
    /**
     * 重复数量
     */
    count: number;
    /**
     * 排列弧度默认360度(一周)
     * @default 360
     */
    radian?: number;
    /**
     * 绕y轴旋转几列360列即为球形 默认1列
     * @default 1
     */
    column?: number;
    /**
     * 旋转角度同步旋转
     * @default false
     */
    syncRotation?: boolean;
}

/**
 * 根据一个模型生成一个围绕一圈的组合
 */
export const modelSurround = ({
    model,
    radius,
    count,
    radian = 360,
    column = 1,
    syncRotation = false,
}: IModelSurround) => {
    const group = new THREE.Group();
    const onceAngle = (Math.PI * 2) / 360; //一度
    const spaceAngle = (radian / count) * onceAngle; //两个物体中间的夹角度数

    for (let l = 0; l < column; l++) {
        const columnGroup = new THREE.Group();
        for (let i = 0; i < count; i++) {
            const item = typeof model === "function" ? model().clone() : model.clone();
            const x = Math.sin(spaceAngle * i) * radius;
            const y = Math.cos(spaceAngle * i) * radius;
            item.position.set(x, y, 0);
            columnGroup.add(item);
            syncRotation && (item.rotation.z = spaceAngle * -i);
        }
        columnGroup.rotation.y = (360 / column) * onceAngle * l;
        group.add(columnGroup);
    }

    return group;
};
/**
 * 包装 物体的 userData对象 控制触发函数
 */
export const intervalObj = (obj: Record<string, any>, ms?: number) => {
    obj.allow = true;
    const prevEffect = obj.effect;
    obj.effect = () => {
        obj.allow = false;
        prevEffect();
        setTimeout(() => {
            obj.allow = true;
        }, ms || 1000);
    };
    return obj;
};

/**
 * 生序排列模型的子元素
 */
export const sortMeshChildren = (children: THREE.Mesh[]) => {
    //生序排列
    children.sort((x, y) => {
        return x.geometry.attributes.position.count - y.geometry.attributes.position.count;
    });
    return children;
};

/**
 * 设置模型透明
 */
export const transparentModel = (model: THREE.Mesh) => {
    const transparentMaterial = new THREE.MeshBasicMaterial({
        transparent: true,
        opacity: 0,
    });
    model.material = transparentMaterial;
};

/**
 * 兜底的地下物体 防止人物跌落
 */
// const undergrounds: Record<string, { mesh: any; update?: () => void }> = {
//     [HomePlanet.name]: HomeUnderground,
//     [ForestPlanet.name]: ForestUnderground,
// };

/**
 * 当前人物在哪个星球中
 */
export function ActerInWherePlanet(onChange: (curr: string, prev: string) => void) {
    let currPlanet = "";
    let prevPlanet = "";

    const planets = [Home, Forest];
    //星球大小，位置不会变 可以使用固定数据
    let planetsBox3 = [] as { planet: THREE.Object3D; box3: THREE.Box3 }[];

    /**
     * 更新星球坐标
     */
    const updatePlanets = () => {
        planetsBox3 = planets.map(({ planet }) => ({
            planet,
            box3: new Box3().setFromObject(planet),
        }));
        // Object.values(undergrounds).forEach((underground) => {
        //     underground.update && underground.update();
        // });
    };

    const update = (acterPosition: THREE.Vector3) => {
        //更新人物当前位置
        for (let i = 0; i < planetsBox3.length; i++) {
            const { planet, box3 } = planetsBox3[i];
            // const isIn = box3.containsBox(acterBox3);
            const isIn = box3.containsPoint(acterPosition);

            if (isIn) {
                prevPlanet = currPlanet;
                currPlanet = planet.name;
                break;
            }
        }

        if (prevPlanet !== currPlanet) {
            // console.log("进入", currPlanet);
            // console.log("离开", prevPlanet);
            onChange(currPlanet, prevPlanet);
        }
    };
    return { update, updatePlanets };
}

/**
 * @description: 销毁物体对象
 * @param {Object3D} THREE.Object3D 销毁的物体
 * @param {parent} THREE.Object3D 销毁的物体的父级，从父级移除物体
 * @return {void}
 * @文档: https://threejs.org/docs/#manual/zh/introduction/How-to-dispose-of-objects
 */
export const destroyObject = (object: THREE.Object3D, parent: THREE.Object3D) => {
    parent.remove(object);
    const children = object.children as THREE.Mesh[];
    if (!children) return;
    children.forEach(({ geometry, material, children }) => {
        geometry.dispose();
        if (Array.isArray(material)) {
            material.forEach((m) => m.dispose());
        } else material?.dispose();
        if (children.length) children.forEach((item) => destroyObject(item, object));
    });
};

/**
 * @description: 缓解镜头进入物体内部问题 设置物体单面显示 进入物体内部看不到物体来解决问题
 * @param {THREE} mesh
 * @return {*}
 */
export const setOneSideMaterial = (object: THREE.Object3D, side: "BackSide" | "DoubleSide" | "FrontSide") => {
    if (object.children.length) {
        object.children.forEach((m) => setOneSideMaterial(m, side));
    }
    if (object.type === "Mesh") {
        const mesh = object as THREE.Mesh;
        if (Array.isArray(mesh.material)) {
            mesh.material.forEach((m) => (m.side = THREE[side]));
        } else mesh.material.side = THREE[side];
    }
};

interface AnimationInstance {
    callback: () => void;
    part: number;
    count: number;
    complete?: () => void;
}
/**
 * 临时的动画控制器
 */
const AnimationControl = () => {
    let animationArr: AnimationInstance[] = [];

    function add(animate: AnimationInstance) {
        animationArr.push(animate);
    }

    function update() {
        animationArr = animationArr.filter((animate) => animate.count < animate.part);
        for (let index = 0; index < animationArr.length; index++) {
            const animate = animationArr[index];
            animate.callback();
            if (animate.count === animate.part && animate.complete) animate.complete();
        }
    }

    return { add, update };
};

export const animationControl = AnimationControl();

/**
 * 性能状态显示
 */
export const stats = {
    stats: undefined as unknown as Stats,
    init: function () {
        const stats = new Stats();
        this.stats = stats;
        document.body.appendChild(stats.dom);
        stats.dom.style.top = "auto";
        stats.dom.style.bottom = "0";
    },
    update: function () {
        this.stats.update();
    },
};

/**
 * 遍历three对象 Object3D 本身提供  traverse traverseVisible traverseAncestors 方法供使用
 */
function cycle(arr: THREE.Object3D[], callback: (obj: THREE.Object3D) => void) {
    for (let i = 0; i < arr.length; i++) {
        const mesh = arr[i];
        mesh.type === "Mesh" && callback(mesh);
        if (mesh.children.length) {
            cycle(mesh.children, callback);
        }
    }
}

let uphillTimer: NodeJS.Timeout;

export const dirType = (moveForward: boolean, moveBackward: boolean, moveRight: boolean) => {
    return moveForward ? "forward" : moveBackward ? "back" : moveRight ? "right" : "left";
};

const defaultUphillHeight = 1;
/**
 * 上坡检测 小的弧度/高度可以走上去 条件是脚下有物体 物体不能高于膝盖
 */
export function uphillCheck(
    controls: any,
    direction: any,
    nearbyObjects: THREE.Object3D[],
    hdis: number
): [number, number, boolean] {
    let uphillHeight = 0;
    let hillAngle = 0;
    let isUphill = false;
    const helpRayHeight = 0.25 * acterScaleMultiple;
    // step1 根据方向确定角度和检测距离
    const origin = controls.getObject().position.clone();
    //step2 脚前方障碍检测
    const mash = frontOfFoot(controls, nearbyObjects, direction, origin)[0];
    // const mash = collideCheck(controls, nearbyObjects, 0, angle, checkDis)[0];
    if (mash) {
        //step3 膝盖检测 障碍高度不能太高 判断能不能迈过去 即高处有没有物体
        const kneeMash = kneeCollide(nearbyObjects, origin)[0];
        //kneeMash && console.log("kneeMash", kneeMash);
        if (!kneeMash) {
            // step4 可以上升 发射一条辅助射线 只需检测目标元素即可
            //辅助线的高度 也是 垂直直角边的长度(两条射线的高度之差) 也是 没有检测到物体时默认的上升高度
            const helpRay = uphillCollide(controls, [mash.object])[0];
            if (helpRay) {
                // const hdis =
                //   moveForward || moveBackward ? forwardDistance : rightDistance;
                //两条射线与物体相交 用勾股定理获取上坡的倾斜度
                //水平直角边长度
                const hl = Math.abs(helpRay.distance - mash.distance);
                //公式 α=arctan(a/b)
                const angle = Math.atan(hl / helpRayHeight) / Math.PI / 2;
                //console.log((angle * 100).toFixed(0) + "deg");
                hillAngle = angle;
                //类似台阶结构 90度
                if (angle < 0.1) {
                    uphillHeight = defaultUphillHeight * acterScaleMultiple;
                } else uphillHeight = Math.abs(hdis * Math.tan(angle));
            } else uphillHeight = helpRayHeight;
            isUphill = true;
            clearTimeout(uphillTimer);
            //稍后设置回false 让fall有处理几次缓慢下落有一个过程 否则会卡顿 高度一上一下 不好看
            uphillTimer = setTimeout(() => {
                isUphill = false;
            }, 100);
        }
    }
    // console.log(uphillHeight);
    return [uphillHeight, hillAngle, isUphill];
}

export const nearCheck = {
    _vector: new THREE.Vector3(),
    position: new THREE.Vector3(),
    nearbyObjects: [] as THREE.Object3D[],
    cycle: function (objects: THREE.Object3D[]) {
        for (let i = 0; i < objects.length; i++) {
            const mesh = objects[i];
            if (mesh.children.length) {
                this.cycle(mesh.children);
            }
            // mesh.type === "Mesh" && callback(mesh);
            if (mesh.type === "Mesh") {
                mesh.getWorldPosition(this._vector);
                const dis = this._vector.distanceTo(this.position);
                if (dis < collideParams.checkDistance) {
                    this.nearbyObjects.push(mesh);
                }
            }
        }
        //@ts-ignore
        window.nearbyObjects = nearCheck.nearbyObjects;
    },
    calculateNearbyObjects: (mustBeDetected: TPlanet["mustBeDetected"], objects: THREE.Object3D[]) => {
        //检测的局限： 以物体的中心坐标计算物体半径大于检测距离 检测不到
        //地面由于过大 手动添加
        nearCheck.nearbyObjects = [...mustBeDetected];
        nearCheck.position = acterWrap.position;
        nearCheck.cycle(objects);

        // console.log("position", position);
        // nearCheck.cycle(objects, (mesh) => {
        //     mesh.getWorldPosition(nearCheck._vecter);
        //     const dis = nearCheck._vecter.distanceTo(position);
        //     if (dis < collideParams.checkDistance) {
        //         nearbyObjects.push(mesh);
        //     }
        // });
        // console.log(nearbyObjects);
        // return nearbyObjects;
    },
};
